﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary
{
    /// <summary>
    /// This class provides the determinant calulating of a square matrix by using the Laplace 
    /// formula.
    /// </summary>
    [Serializable]
    public class LaplaceDeterminantCalculation
    {
        /// <summary>
        /// The source matrix from which the determinant should be calculated.
        /// </summary>
        private SquareMatrix sourceMatrix;

        /// <summary>
        /// Initializes a new instance of the <see cref="LaplaceDeterminantCalculation"/> class.
        /// </summary>
        /// <param name="sourceMatrix">The source matrix from which the determinant should 
        /// be calculated.</param>
        public LaplaceDeterminantCalculation(SquareMatrix sourceMatrix)
        {
            this.sourceMatrix = sourceMatrix;
        }

        /// <summary>
        /// Gets or sets the source matrix from which the determinant should be calculated.
        /// </summary>
        /// <value>The source matrix from which the determinant should be calculated.</value>
        public SquareMatrix SourceMatrix
        {
            get { return sourceMatrix; }
            set { sourceMatrix = value; }
        }

        /// <summary>
        /// Calculates the determinant by using the formula of Laplace.
        /// </summary>
        /// <returns>The determinant of the specified matrix.</returns>
        public double CalculateDeterminant()
        {
            return -this.InternalCalculateDeterminant(this.sourceMatrix.MatrixData);
        }

        /// <summary>
        /// Creates the needed part of the matrix for using the Laplace formula.
        /// </summary>
        /// <param name="matrix">The matrix to split.</param>
        /// <param name="column">The column to split.</param>
        /// <param name="row">The row to split.</param>
        /// <returns>A part of the specified matrix.</returns>
        private static double[,] CreateMatrixPart(double[,] matrix, int column, int row)
        {
            double[,] new_matrix = new double[(matrix.GetLength(0) - 1),(matrix.GetLength(1) - 1)];

            for (int y = 0; y < column; y++)
            {
                for (int x = 0; x < row; x++)
                {
                    new_matrix[x, y] = matrix[x, y];
                }
            }

            for (int y = (column + 1); y < matrix.GetLength(1); y++)
            {
                for (int x = 0; x < row; x++)
                {
                    new_matrix[x, (y - 1)] = matrix[x, y];
                }
            }

            for (int y = 0; y < column; y++)
            {
                for (int x = (row + 1); x < matrix.GetLength(0); x++)
                {
                    new_matrix[(x - 1), y] = matrix[x, y];
                }
            }

            for (int y = (column + 1); y < matrix.GetLength(1); y++)
            {
                for (int x = (row + 1); x < matrix.GetLength(0); x++)
                {
                    new_matrix[(x - 1), (y - 1)] = matrix[x, y];
                }
            }

            return new_matrix;
        }

        /// <summary>
        /// Calculate the determinant of a square matrix by using the Laplace formula.
        /// </summary>
        /// <param name="matrixData">The matrix, from which the determinant should be
        /// calculated.</param>
        /// <returns></returns>
        private double InternalCalculateDeterminant(double[,] matrixData)
        {
            if (matrixData.GetLength(0) == 2 && matrixData.GetLength(1) == 2)
            {
                return ((matrixData[0, 0] * matrixData[1, 1]) - (matrixData[0, 1] * matrixData[1, 0]));
            }

            double result = 0;
            bool negative = false;

            for (int i = 0; i < matrixData.GetLength(1); i++)
            {
                if (negative)
                {
                    result += (-1) * matrixData[1, i] *
                              this.InternalCalculateDeterminant(CreateMatrixPart(matrixData, i, 1));
                    negative = false;
                }
                else
                {
                    result += matrixData[1, i] * this.InternalCalculateDeterminant(CreateMatrixPart(matrixData, i, 1));
                    negative = true;
                }
            }

            return result;
        }
    }
}